﻿using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.WebApi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Sino.CapacityCloud.Repositories.Models
{

    public static class ApplicationDbInitializer
    {

        public const string DefaultAdminUserNameKey = "DefaultAdminUserName";
        public const string DefaultAdminPasswordKey = "DefaultAdminPassword";
        public const string DefaultAdminPhoneNumberKey = "DefaultAdminPhoneNumber";

        public const string ConfigurationFileName = "appsettings";

        public static IConfiguration Configuration { get; private set; }

        public static void Seed(IServiceProvider serviceProvider, bool createUsers = true)
        {
            using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                var appEnv = serviceProvider.GetService<IApplicationEnvironment>();
                var env = serviceScope.ServiceProvider.GetService<IHostingEnvironment>();
                var configurationBuilder = new ConfigurationBuilder()
                   .SetBasePath(appEnv.ApplicationBasePath)
                   .AddJsonFile($"{ConfigurationFileName}.json")
                   .AddJsonFile($"{ConfigurationFileName}.{env.EnvironmentName}.json", optional: true)
                   .AddEnvironmentVariables();

                ApplicationDbInitializer.Configuration = configurationBuilder.Build();
            }

            SeedAsync(serviceProvider, createUsers).GetAwaiter().GetResult();
        }

        private static async Task SeedAsync(IServiceProvider serviceProvider, bool createUsers = true)
        {
            using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                var logger = serviceScope.ServiceProvider.GetService<ILoggerFactory>()?.CreateLogger(typeof(ApplicationDbInitializer).FullName);
                using (var db = serviceScope.ServiceProvider.GetService<ApplicationDbContext>())
                {
                    var env = serviceScope.ServiceProvider.GetService<IHostingEnvironment>();
                    logger?.LogInformation($"Begin to seed the database with db != null: {db != null}");
                    logger?.LogInformation($"Using connection string: {db.Database.GetDbConnection().ConnectionString}");

                    // TODO: Sqlite cannot make sure the db has been created.
                    // SqlServer will make creation operation and then return false, but the Sqlite will return true.
                    if (db.Database.EnsureCreated())
                    {
                        logger?.LogInformation("The database has been created.");
                    }
                    else
                    {
                        logger?.LogInformation("The database has been existed.");
                    }

                    logger?.LogInformation("The database has been created. Seeding the database.");
                    if (createUsers)
                    {
                        logger?.LogInformation("Creating users and groups.");
                        var adminUser = await CreateAdminUser(serviceProvider, logger);
                        if (adminUser == null)
                        {
                            goto DoneSeed;
                        }
                    }

                    DoneSeed:
                    logger?.LogInformation("Done seeding the database.");
                }
            }
        }

        private static async Task<ApplicationUser> CreateAdminUser(IServiceProvider serviceProvider, ILogger logger)
        {
            try
            {
                var appEnv = serviceProvider.GetService<IApplicationEnvironment>();

                var configuration = Configuration.GetSection("Administrator");

                var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
                var roleManagerAvailable = false;

                // TODO: Identity SQL does not support roles yet
                IdentityResult identityResult;
                var roleManager = serviceProvider.GetService<RoleManager<ApplicationRole>>();
                var roleNames = typeof(ApplicationRole).GetRuntimeFields().Where(x => x.IsStatic).Select(x => (string)x.GetValue(null)).ToList();
                if (roleManager != null && !await roleManager.RoleExistsAsync(ApplicationRole.Administator))
                {
                    roleManagerAvailable = true;
                    foreach (var roleName in roleNames)
                    {
                        identityResult = await roleManager.CreateAsync(new ApplicationRole(roleName));
                        if (!identityResult.Succeeded)
                        {
                            logger.LogError($"Failed to create role {roleName}.");
                            roleManagerAvailable = false;
                            break;
                        }
                    }
                }

                var user = await userManager.FindByNameAsync(configuration[DefaultAdminUserNameKey]);
                if (user == null)
                {
                    user = new ApplicationUser()
                    {
                        UserName = configuration[DefaultAdminUserNameKey],
                        Email = configuration[DefaultAdminUserNameKey],
                        PhoneNumber = configuration[DefaultAdminPhoneNumberKey],
                        CertificatePhotoCertificationState = CertificationStatus.Uncommitted,
                        DrivingLicenseKeyCertificationState = CertificationStatus.Uncommitted,
                        IdentityCardCertificationState = CertificationStatus.Uncommitted
                    };
                    identityResult = await userManager.CreateAsync(user, configuration[DefaultAdminPasswordKey]);
                    if (identityResult.Succeeded)
                    {
                        if (roleManagerAvailable)
                        {
                            foreach (var roleName in roleNames)
                            {
                                identityResult = await userManager.AddToRoleAsync(user, roleName);
                                if (!identityResult.Succeeded)
                                {
                                    logger.LogError($"Failed to add the user {user.Id} to role {roleName}.");
                                }
                            }
                        }
                        await userManager.UpdateAsync(user);
                        identityResult = await userManager.AddClaimsAsync(user, new Claim[]
                        {
                            new Claim(ApplicationClaim.Manage, ApplicationClaim.Allowed),
                            new Claim(ApplicationClaim.WebAPI, ApplicationClaim.Allowed)
                        });
                        if (!identityResult.Succeeded)
                        {
                            logger.LogError($"Failed to add claims for {user.Id}.");
                        }
                        await userManager.UpdateAsync(user);
                        return user;
                    }
                    else
                    {
                        logger.LogError($"Failed to create the Administrator user. {string.Join("\n", identityResult.ToErrorStrings())}");
                    }
                }

                return user;
            }
            catch (Exception ex)
            {
                logger.LogError(ex.Message, ex);
            }

            return null;
        }

    }

}